package space.hyf.config.security;

import space.hyf.entity.LoginLog;
import space.hyf.entity.User;
import space.hyf.exception.BadRequestException;
import space.hyf.model.vo.Result;
import space.hyf.service.LoginLogService;
import space.hyf.utils.IpAddressUtils;
import space.hyf.utils.JacksonUtils;
import space.hyf.utils.JwtUtils;
import space.hyf.utils.StringUtils;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description: Jwt登录处理过滤器
 * @ClassName: JwtLoginFilter
 * @Author: HCode
 * @Date: 2023/3/14 15:11
 * @Version: 1.0
 */
public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter {

    LoginLogService loginLogService;  // 登录日志处理
    ThreadLocal<String> currentUsername = new ThreadLocal<>();

    /**
     * 有参构造
     *
     * @param defaultFilterProcessesUrl 处理的URL
     * @param authenticationManager     认证管理
     * @param loginLogService           登录日志服务
     */
    protected JwtLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager, LoginLogService loginLogService) {
        super(new AntPathRequestMatcher(defaultFilterProcessesUrl));
        setAuthenticationManager(authenticationManager);
        this.loginLogService = loginLogService;
    }

    /**
     * 处理前端发送的表单数据
     *
     * @param request  请求体
     * @param response 响应体
     * @return 无
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException {
        try {
            // 后台登录请求必须是post
            if (!"POST".equals(request.getMethod())) {
                throw new BadRequestException("请求方法错误");
            }
            // 解析从前端获取的用户登录表单创建你用户对象
            User user = JacksonUtils.readValue(request.getInputStream(), User.class);
            currentUsername.set(user.getUsername());
            return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()));
        } catch (BadRequestException exception) {
            response.setContentType("application/json;charset=utf-8");
            Result result = Result.create(400, "非法请求");
            PrintWriter out = response.getWriter();
            out.write(JacksonUtils.writeValueAsString(result));
            out.flush();
            out.close();
        }
        return null;
    }

    /**
     * 成功认证的过滤器
     *
     * @param request    请求头
     * @param response   响应体
     * @param chain      过滤链
     * @param authResult 认证结果
     * @throws IOException IO异常
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException {
        String jwt = JwtUtils.generateToken(authResult.getName(), authResult.getAuthorities());
        response.setContentType("application/json;charset=utf-8");
        User user = (User) authResult.getPrincipal();
        user.setPassword(null);
        Map<String, Object> map = new HashMap<>(4);
        map.put("user", user);
        map.put("token", jwt);
        System.out.println(user.toString());
        Result result = Result.ok("登录成功", map);
        PrintWriter out = response.getWriter();
        out.write(JacksonUtils.writeValueAsString(result));
        out.flush();
        out.close();
        LoginLog log = handleLog(request, true, "登录成功");
        loginLogService.saveLoginLog(log);
    }

    /**
     * 表示身份校验失败之后调用方法
     *
     * @param request   请求体
     * @param response  响应体
     * @param exception 失败认证异常
     * @throws IOException
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
        response.setContentType("application/json;charset=utf-8");
        String msg = exception.getMessage();
        //登录不成功时，会抛出对应的异常
        if (exception instanceof LockedException) {
            msg = "账号被锁定";
        } else if (exception instanceof CredentialsExpiredException) {
            msg = "密码过期";
        } else if (exception instanceof AccountExpiredException) {
            msg = "账号过期";
        } else if (exception instanceof DisabledException) {
            msg = "账号被禁用";
        } else if (exception instanceof BadCredentialsException) {
            msg = "用户名或密码错误";
        }
        PrintWriter out = response.getWriter();
        out.write(JacksonUtils.writeValueAsString(Result.create(401, msg)));
        out.flush();
        out.close();
        LoginLog log = handleLog(request, false, StringUtils.substring(msg, 0, 50));
        loginLogService.saveLoginLog(log);
    }


    /**
     * 保存登录日志
     *
     * @param request     请求对象
     * @param status      登录状态
     * @param description 操作描述
     */
    private LoginLog handleLog(HttpServletRequest request, boolean status, String description) {
        String username = currentUsername.get();
        currentUsername.remove();
        String ip = IpAddressUtils.getIpAddress(request);
        String userAgent = request.getHeader("User-Agent");
        LoginLog log = new LoginLog(username, ip, status, description, userAgent);
        return log;
    }
}
